//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ActivitiesSyncLibrary
{
    #region Using Directives
    
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Workflow.Activities;
    using System.Xml;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using CM = System.Workflow.ComponentModel;
using Microsoft.Research.ScientificWorkflow.UIDesigner;
    using System.Diagnostics;

    #endregion

    /// <summary>
    /// This class contains the logic related to update all the activities information from 
    /// existing library folder to registry.
    /// </summary>
    public class SyncActivites
    {
        /// <summary>
        /// constant string name.
        /// </summary>
        private const string Name = "Name";

        /// <summary>
        /// Variable for connection of registry.
        /// </summary>
        private Connection registry;

        /// <summary>
        /// UI Helper.
        /// </summary>
        private UIHelper uiHelper;

        /// <summary>
        /// InBuiltActivity collections.
        /// </summary>
        private Dictionary<string, Type> inBuiltActivities = null;

        /// <summary>
        /// ActivityComposer object.
        /// </summary>
        private ActivityComposer activityComposer = null;

        /// <summary>
        /// Workflow composer object
        /// </summary>
        private WorkflowComposer workflowComposer = null;

        /// <summary>
        /// Constructor of SyncActivites.
        /// </summary>
        /// <param name="registryConnection">Registry connection object.</param>
        public SyncActivites(Connection registryConnection)
        {
            try
            {
                if (registryConnection == null)
                {
                    TridentErrorHandler.ThrowTridentCustomException(TridentErrorConstants.ErrorNumber10000001, TridentResourceManager.GetString("SyncConnectionNullError"));
                }
                else if (!registryConnection.Alive)
                {
                    TridentErrorHandler.ThrowTridentCustomException(TridentErrorConstants.ErrorNumber10000002, TridentResourceManager.GetString("SyncConnectionAliveError"));
                }

                this.registry = registryConnection;
                this.activityComposer = new ActivityComposer(this.registry, true);
                this.workflowComposer = new WorkflowComposer(this.registry, activityComposer);
                this.uiHelper = new UIHelper(this.registry);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public void IntializeUserRoles(Connection registryConnection, String userName, String domainName)
        {
            try
            {
                // Intialize System Policy
                SystemPolicy policy = null;
                SystemPolicy.Initialize(true, true, registryConnection);

                // Adding Builtin Groups
                // Add Administrator Group to registry
                Group adminGroup = Group.Create("Administrators", this.registry);
                adminGroup.Enabled = true;
                adminGroup.IsBuiltin = true;
                adminGroup.Save();

                // Add Users Group to registry
                Group userGroup = Group.Create("Users", this.registry);
                userGroup.Enabled = true;
                userGroup.IsBuiltin = true;
                userGroup.Save();

                // Add Services Group to registry
                Group servicesGroup = Group.Create("Services", this.registry);
                servicesGroup.Enabled = true;
                servicesGroup.IsBuiltin = true;
                servicesGroup.Save();

                // Add Guest Group to registry
                Group guestGroup = Group.Create("Guests", this.registry);
                guestGroup.Enabled = true;
                guestGroup.IsBuiltin = true;
                guestGroup.Save();

                // Add the Everyone group to registry
                Group everyone = Group.Create("Everyone", this.registry);
                everyone.Enabled = false;
                everyone.IsBuiltin = true;
                everyone.Save();

                policy = SystemPolicy.Get(this.registry);
                policy.Builtin.Administrators.Add(adminGroup);
                policy.Builtin.Users.Add(userGroup);
                policy.Builtin.Services.Add(servicesGroup);
                policy.Builtin.Guests.Add(guestGroup);

                policy.Refresh();

                // Adding the logged-in user to the database.
                User loggedInUser = User.Create(userName, this.registry);
                loggedInUser.Enabled = true;
                loggedInUser.Save();
                loggedInUser.IsBuiltin = true;
                String credentialName = string.Format(@"{0}\{1}", domainName, userName);
                UserCredential loggedInUserCredential = UserCredential.Create(credentialName, CredentialType.Windows, loggedInUser, this.registry);
                loggedInUserCredential.Save();

                // Create a workflow root if it doesnt exist.
                TridentAuthentication.CreateMyWorkflowCategory(loggedInUser, this.registry);
                // Add to the admin group.
                adminGroup.Users.Add(loggedInUser);
                // Add to Admin role.
                policy.Builtin.Administrators.Add(loggedInUser);

                TridentAuthentication authendicate = new TridentAuthentication(this.registry);

                User systemUser = User.Get("system", this.registry);
                if (systemUser != null)
                {
                    systemUser.IsBuiltin = true;
                    policy.Builtin.Administrators.Add(systemUser);
                    policy.Refresh();
                }

                User anonymousUser = User.Create("Anonymous", this.registry);
                anonymousUser.Enabled = true;
                anonymousUser.IsBuiltin = true;
                anonymousUser.Save();
                string credential = string.Format("NT Authority\\NETWORK SERVICE");
                UserCredential userCredential = UserCredential.Create(credential, CredentialType.Windows, anonymousUser, this.registry);
                userCredential.Save();

                anonymousUser.Refresh();
                this.registry.StorageSecurity.GrantDirectAccess(anonymousUser, StorageRole.Administrator);

                policy.Builtin.Guests.Add(anonymousUser);
                policy.Refresh();

            }
            catch (Exception)
            {

            }
        }

        /// <summary>
        /// Method for sync activities from library to registry. 
        /// If any activity information is missing in registry then update that activity information in registry.
        /// </summary>
        /// <param name="libraryPath">Activity Library path.</param>
        public void SyncActivitiesToRegistry(string libraryPath)
        {
            string wfAppExeLocation = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "WorkflowApplication.exe");
            string wfAppIconLocation = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "WorkflowApplicationIcon.ico");

            if (!Directory.Exists(libraryPath))
            {
                throw new DirectoryNotFoundException("{0} directory not exist.");
            }

            try
            {
                // Create system user's categories.
                CategoriesComposer.CreateSystemUserCategories(registry);

                // Make collection of in-built activities.
                this.BuildBuiltInModels();

                Assembly me = Assembly.GetExecutingAssembly();
                string myPath = Path.Combine(Path.GetDirectoryName(me.Location), "InBuiltActivity.xml");

                // Sync in-built activities.
                this.SyncActivities(myPath, libraryPath);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Method for sync activities from inbuilt activities collection to registry based on present inbuiltactivity.xml file. 
        /// If any activity information is missing in registry then update that activity information in registry.
        /// </summary>
        /// <param name="fileName"></param>
        private void SyncActivities(string fileName, string libraryPath)
        {
            string name = string.Empty;
            Activity act = null;
            try
            {
                Namespace activityNamespace = null;
                try
                {
                    User user = User.Get(CategoriesComposer.SystemUserName, registry);

                    // Lack of time, adding a hardcoded Windows Workflow namespace for all buitin
                    // Later we may change it to driver everything from XML including category
                    activityNamespace = CategoriesComposer.GetActivityNamespace(CategoriesComposer.BuiltInActivitiesName, registry, true);
                }
                catch
                {

                }

                //creating a text reader for the XML file already picked by the 
                using (XmlTextReader objXmlTextReader = new XmlTextReader(fileName))
                {
                    objXmlTextReader.MoveToContent();
                    while (objXmlTextReader.Read())
                    {
                        if (objXmlTextReader.NodeType == XmlNodeType.Element)
                        {
                            if (objXmlTextReader.AttributeCount > 1)
                            {
                                string attribName = objXmlTextReader.GetAttribute(0);
                                string type = objXmlTextReader.GetAttribute(1);
                                string hiddenAttribute = string.Empty;
                                if (objXmlTextReader.AttributeCount > 2)
                                {
                                    hiddenAttribute = objXmlTextReader.GetAttribute(2);
                                }

                                //Check whether the current node is a built in activity
                                if (0 == type.ToLower().CompareTo("builtin"))
                                {
                                    if (inBuiltActivities[attribName] != null)
                                    {
                                        bool status = true;

                                        try
                                        {
                                            act = Activity.Load(inBuiltActivities[attribName].Name, registry);
                                        }
                                        catch (RecordNotFoundException)
                                        {
                                            status = false;
                                        }

                                        if (!status)
                                        {
                                            act = activityComposer.CreateActivity(inBuiltActivities[attribName], new Collection<FileReference>(), activityNamespace, null);
                                        }

                                        act.IsBuiltIn = true;

                                        // Update the Hidden attribute
                                        if (!string.IsNullOrEmpty(hiddenAttribute) && 0 == hiddenAttribute.ToLower().CompareTo("true"))
                                        {
                                            act.IsHidden = true;
                                        }
                                    }
                                }
                                //Handle trident activity
                                else if (0 == type.ToLower().CompareTo("tridentactivity"))
                                {
                                    //get the dll name
                                    string strDllName = objXmlTextReader.GetAttribute(2);
                                    string strMyFile = Path.Combine(Path.Combine(Path.GetDirectoryName(fileName), "Libraries"), strDllName);
                                    Collection<FileReference> fileReferences = new Collection<FileReference>();
                                    Helper.BuildReferences(fileReferences, strMyFile, Path.GetDirectoryName(strMyFile));

                                    string innerXml = objXmlTextReader.ReadInnerXml();
                                    if (!string.IsNullOrEmpty(innerXml))
                                    {
                                        XmlDocument xmlDoc = new XmlDocument();
                                        xmlDoc.LoadXml(innerXml);
                                        foreach (XmlNode child in xmlDoc.ChildNodes)
                                        {
                                            string path = Path.Combine(Path.GetDirectoryName(fileName), "Libraries");
                                            path = Path.Combine(path, child.Attributes["Name"].Value);

                                            FileReference temp = fileReferences.FirstOrDefault(F => F.DisplayName.Equals(child.Attributes["Name"].Value, StringComparison.OrdinalIgnoreCase));
                                            if (temp == null)
                                            {
                                                // If this reference is not already added, add it.
                                                FileReference mainRef = fileReferences.FirstOrDefault(F => F.DisplayName.Equals(Path.GetFileName(strMyFile), StringComparison.OrdinalIgnoreCase));
                                                if (mainRef != null)
                                                {
                                                    mainRef.References.Add(path);
                                                    mainRef.DisplayReferences.Add(child.Attributes["Name"].Value);
                                                }

                                                FileReference newRef = new FileReference(path, new Collection<string>());
                                                fileReferences.Add(newRef);
                                            }
                                        }
                                    }

                                    foreach (FileReference fileReference in fileReferences)
                                    {
                                        Helper.GetImageFromRegistry(fileReference, this.registry);
                                    }

                                    ImportActivity importActivity = new ImportActivity(this.registry, true);
                                    importActivity.ImportActivities(fileReferences, new Dictionary<string, Activity>(), false);
                                }
                                else if (0 == type.ToLower().CompareTo("tridentworkflow"))
                                {
                                    //Handle out of the box trident workflow
                                    ImportWorkflow(objXmlTextReader, libraryPath);
                                }
                                else if (0 == type.ToLower().CompareTo("tridentworkflowpackage"))
                                {
                                    //Handle out of the box trident workflow
                                    ImportWorkflowPackage(objXmlTextReader, libraryPath);
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(ex, TridentErrorConstants.ErrorNumber10000003, TridentResourceManager.GetString("SyncInBuiltError"));
            }
            finally
            {
                name = null;
                act = null;
            }
        }

        /// <summary>
        /// Build look up tables for activities built in to the Workflow Library, and thus not available for attribution
        /// </summary>
        private void BuildBuiltInModels()
        {
            inBuiltActivities = new Dictionary<string, System.Type>();

            // Composite Built-Ins
            inBuiltActivities.Add("DelayActivity", typeof(DelayActivity));
            inBuiltActivities.Add("SequenceActivity", typeof(SequenceActivity));
            inBuiltActivities.Add("SequentialWorkflowActivity", typeof(SequentialWorkflowActivity));
            inBuiltActivities.Add("ParallelActivity", typeof(ParallelActivity));
            inBuiltActivities.Add("IfElseActivity", typeof(IfElseActivity));
            inBuiltActivities.Add("IfElseBranchActivity", typeof(IfElseBranchActivity));
            inBuiltActivities.Add("IfElseBranchActivity.Condition", typeof(CM.ActivityCondition));
            inBuiltActivities.Add("ReplicatorActivity", typeof(ReplicatorActivity));
            inBuiltActivities.Add("ListenActivity", typeof(ListenActivity));
            inBuiltActivities.Add("WhileActivity", typeof(WhileActivity));
            inBuiltActivities.Add("SuspendActivity", typeof(CM.SuspendActivity));
            inBuiltActivities.Add("TerminateActivity", typeof(CM.TerminateActivity));
            inBuiltActivities.Add("ThrowActivity", typeof(CM.ThrowActivity));
            inBuiltActivities.Add("InvokeWorkflowActivity", typeof(InvokeWorkflowActivity));
            inBuiltActivities.Add("EventHandlingScopeActivity", typeof(EventHandlingScopeActivity));
            inBuiltActivities.Add("EventDrivenActivity", typeof(EventDrivenActivity));
            inBuiltActivities.Add("StateActivity", typeof(StateActivity));
            inBuiltActivities.Add("StateFinalizationActivity", typeof(StateFinalizationActivity));
            inBuiltActivities.Add("StateMachineWorkflowActivity", typeof(StateMachineWorkflowActivity));
            inBuiltActivities.Add("SetStateActivity", typeof(SetStateActivity));
            inBuiltActivities.Add("WebServiceInputActivity", typeof(WebServiceInputActivity));
            inBuiltActivities.Add("WebServiceFaultActivity", typeof(WebServiceFaultActivity));
            inBuiltActivities.Add("CodeActivity", typeof(CodeActivity));
            inBuiltActivities.Add("EventHandlersActivity", typeof(EventHandlersActivity));
            inBuiltActivities.Add("PolicyActivity", typeof(PolicyActivity));
            inBuiltActivities.Add("CallExternalMethodActivity", typeof(CallExternalMethodActivity));
            inBuiltActivities.Add("ConditionedActivityGroup", typeof(ConditionedActivityGroup));
            inBuiltActivities.Add("HandleExternalEventActivity", typeof(HandleExternalEventActivity));
            inBuiltActivities.Add("InvokeWebServiceActivity", typeof(InvokeWebServiceActivity));
            inBuiltActivities.Add("CompensatableSequenceActivity", typeof(CompensatableSequenceActivity));
            inBuiltActivities.Add("StateInitializationActivity", typeof(StateInitializationActivity));
            inBuiltActivities.Add("WebServiceOutputActivity", typeof(WebServiceOutputActivity));
        }

        /// <summary>
        /// This function imports the out of the box workflows to the system user workflow root 
        /// might fail if the XML properties are not proper, installer will fail with some unknown errors
        /// </summary>
        /// <param name="objXmlTextReader"></param>
        /// <param name="libraryPath"></param>
        private void ImportWorkflow(XmlTextReader objXmlTextReader, string libraryPath)
        {
            Guid namespaceGuid = Guid.Empty;
            Collection<FileReference> references = new Collection<FileReference>();

            string attribName = objXmlTextReader.GetAttribute(0);
            //get the file name
            string filePath = objXmlTextReader.GetAttribute(2);
            //read the workflow category
            string category = objXmlTextReader.GetAttribute(4);
            //read the user whether its a system namespace or user
            string categoryType = objXmlTextReader.GetAttribute(5);
            string isInteractive = objXmlTextReader.GetAttribute(6);

            try
            {
                string userName = CategoriesComposer.SystemUserName;
                if (0 == categoryType.ToLower().CompareTo("user"))
                {
                    userName = Environment.UserName;
                }
                User user = User.Get(userName, registry);
                //Create the namespace
                Namespace workflowNamespace = CategoriesComposer.CreateNamespace(category, registry, user);
                namespaceGuid = ((IObject)workflowNamespace).ID;
            }
            catch
            {
            }

            FileReference fileRef = new FileReference(Path.Combine(libraryPath, filePath), new Collection<string>());
            fileRef.ContainsActivity = false;
            references.Add(fileRef);
            string workflowDescription = objXmlTextReader.GetAttribute(3);
            objXmlTextReader.Read();

            // Loop through the dependency list and get all the dependency files
            while (0 != objXmlTextReader.Name.ToLower().CompareTo("activity"))
            {
                if (XmlNodeType.Element == objXmlTextReader.NodeType)
                {
                    filePath = objXmlTextReader.GetAttribute(0);
                    bool isActivity = false;
                    if (!bool.TryParse(objXmlTextReader.GetAttribute("IsActivity"), out isActivity))
                    {
                        isActivity = false;
                    }

                    fileRef = new FileReference(Path.Combine(libraryPath, filePath), new Collection<string>());
                    fileRef.ContainsActivity = isActivity;
                    references.Add(fileRef);
                }

                objXmlTextReader.Read();
            }

            bool interactive = false;
            if (!bool.TryParse(isInteractive, out interactive))
            {
                interactive = false;
            }

            WorkflowProperties properties = new WorkflowProperties(attribName, workflowDescription, namespaceGuid, interactive);
            this.workflowComposer.ImportWorkflow(references, properties, Environment.UserName, true);
        }

        /// <summary>
        /// Imports the workflow package.
        /// </summary>
        /// <param name="objXmlTextReader"></param>
        /// <param name="libraryPath"></param>
        private void ImportWorkflowPackage(XmlTextReader objXmlTextReader, string libraryPath)
        {
            string packageTempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
            string packageFilePath = Path.Combine(libraryPath, objXmlTextReader.GetAttribute("FileName"));
            Collection<string> workflowFiles = Helper.ExtractPackage(packageFilePath, packageTempDir, true);

            Collection<FileReference> references = new Collection<FileReference>();
            foreach (string path in workflowFiles)
            {
                FileReference fileRef = new FileReference(path, new Collection<string>());
                fileRef.ContainsActivity = Helper.ContainsActivity(path);
                references.Add(fileRef);
            }

            Guid namespaceGuid = Guid.Empty;
            string attribName = objXmlTextReader.GetAttribute("Name");
            string workflowDescription = objXmlTextReader.GetAttribute("Description");
            string category = objXmlTextReader.GetAttribute("Category");
            string categoryType = objXmlTextReader.GetAttribute("CategoryType");
            string isInteractive = objXmlTextReader.GetAttribute("IsInteractive");
            bool interactive = false;
            if (!bool.TryParse(isInteractive, out interactive))
            {
                interactive = false;
            }

            try
            {
                string userName = CategoriesComposer.SystemUserName;
                if (0 == categoryType.ToLower().CompareTo("user"))
                {
                    userName = Environment.UserName;
                }
                User user = User.Get(userName, registry);
                //Create the namespace
                Namespace workflowNamespace = CategoriesComposer.CreateNamespace(category, registry, user);
                namespaceGuid = ((IObject)workflowNamespace).ID;
            }
            catch
            {
            }

            WorkflowProperties properties = new WorkflowProperties(attribName, workflowDescription, namespaceGuid, interactive);
            Guid workflowId = this.workflowComposer.ImportWorkflow(references, properties, Environment.UserName, true);
            if (workflowId != Guid.Empty)
            {
                // Copy the file from installed location to temp directory so that installer has enough privileges.
                string destPackageFile = Path.Combine(packageTempDir, Path.GetFileName(packageFilePath));
                File.Copy(packageFilePath, destPackageFile);

                // Add required metadata to the workflow.
                this.uiHelper.AddMetadata(workflowId, destPackageFile, true);
            }
        }
    }
}